require 'kind_dom/object_proxy'
require 'xml/libxml'

module KindDom

  # KindDom provides graceful access to the the DOM of an XML document using
  # three methods of Kindness: 
  # - <tt>#collection_of</tt> to select a collection of nodes
  # - <tt>#first_of</tt> to select one node
  # - <tt>#content_for</tt> to get node content.
  #
  # The original libxml behavior of the parsed document is preserved.
  #
  # As a contrived example, in the controller:
  #    @results = KindDom.new(xml_data)
  # 
  # In the view:
  #    
  #    <% @results.first_of('//item') do |item| -%>
  #      <p>(This block is only executed if `item` is found.)</p>
  #    
  #      <% item.content_for('title') do |title| -%>
  #        <h2><%=h title %></h2>
  #        <p>(This block is only executed if `title` has content.)</p>
  #      <% end -%>
  #     
  #      <% item.content_for('description') do |description| -%>
  #        <p><%=h description %></p>
  #        <p>(This block is only executed if `description` has content.)</p>
  #      <% end -%>
  #    <% end -%>
  #
  class Base < ObjectProxy
  
    attr_accessor :dom
  
    def initialize(raw_xml=nil)
      unless raw_xml.blank?
        parser = XML::Parser.new
        parser.string = raw_xml
        @dom = parser.parse
      end
    rescue XML::Parser::ParseError
    ensure
      @dom.extend Kindness
      super(@dom) # Proxy all method calls to @dom. 
    end
  
    module Kindness
      # Retrieve the contents of the first node or attribute selected by the XPath.
      #
      # Optional second argument is the default value to return if the DOM find fails.
      #
      # When a block is provided, it will be called with the found content 
      # (or default) before returning.
      #
      def content_for(xpath, default=nil)       # :yields: found_content
        node = case self.class.to_s
        when 'XML::Document' then
          root.find_first(xpath)
        else # 'XML::Node'
          find_first(xpath)
        end
        content = case node.class.to_s
        when 'XML::Attr' then
          node.value
        else
          node.content
        end
      rescue NoMethodError
      ensure
        content = content.blank? ? default : content
        if block_given? and !content.blank?
          return yield(content)
        else
          return content
        end
      end
    
      # Retrieve a collection (Array) of DOM nodes selected by the XPath.
      #
      # Each node is extended with Kindness to support #content_for, #collection_of & #first_of.
      #
      # When a block is provided, it will be called with the found collection 
      # (or default) before returning.
      #
      def collection_of(xpath, default=nil)      # :yields: found_collection
        c = find(xpath).collect {|n| n.extend Kindness }
      rescue NoMethodError
      ensure
        c = c.blank?||c.size<1 ? default : c
        if block_given? and !c.nil?
          return yield(c)
        else
          return c
        end
      end
    
      # Retrieve the first DOM node selected by the XPath.
      #
      # The node is extended with Kindness to support #content_for, #collection_of & #first_of.
      #
      # When a block is provided, it will be called with the found node 
      # (or default) before returning.
      #
      def first_of(xpath, default=nil)      # :yields: found_node
        n = case self.class.to_s
        when 'XML::Document' then
          root.find_first(xpath)
        else # 'XML::Node'
          find_first(xpath)
        end
        n.extend Kindness
      rescue NoMethodError
      ensure
        n = n.blank? ? default : n
        if block_given? and !n.blank?
          return yield(n)
        else
          return n
        end
      end
    end
  
  end
end